package io.gitee.tziye.core.kv.impl;

import io.gitee.tziye.common.RenaiUtil;
import io.gitee.tziye.core.instance.DefaultRenaiClient;
import io.gitee.tziye.core.kv.RenaiKey;
import io.gitee.tziye.core.kv.RenaiValue;
import io.gitee.tziye.enums.KvType;
import io.gitee.tziye.jdbc.JdbcProxy;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;

import java.util.*;

public class KvCommonOperator {

    private final JdbcProxy jdbcProxy;

    public KvCommonOperator(DefaultRenaiClient renaiClient) {
        jdbcProxy = renaiClient.getJdbcProxy();
    }

    public <T> void saveKey(String key, Integer expireSeconds, KvType type) {
        assert StringUtils.isNotBlank(key) && (expireSeconds == null || expireSeconds > 0);
        if (expireSeconds == null) {
            String sql = """
                    INSERT INTO %s(k) VALUES (?) ON DUPLICATE KEY
                    UPDATE expire_time = IF(expire_time <= NOW(), NULL, expire_time), update_time = NOW()""";
            sql = String.format(sql, type.getKTable());
            jdbcProxy.update(sql, key);
        } else {
            String sql = """
                    INSERT INTO %s(k, expire_time) VALUES (?, DATE_ADD(NOW(), INTERVAL ? SECOND))
                    ON DUPLICATE KEY UPDATE expire_time = VALUES(expire_time), update_time = NOW()""";
            sql = String.format(sql, type.getKTable());
            jdbcProxy.update(sql, key, expireSeconds);
        }
    }

    public RenaiKey key(String key, KvType type) {
        assert StringUtils.isNotBlank(key);
        String sql = "SELECT * FROM %s WHERE k = ?";
        sql = String.format(sql, type.getKView());
        List<RenaiKey> renaiKeys = jdbcProxy.query(sql, new BeanPropertyRowMapper<>(RenaiKey.class), key);
        return CollectionUtils.isEmpty(renaiKeys) ? null : renaiKeys.get(0);
    }

    public List<RenaiKey> keys(List<String> keys, KvType type) {
        if (CollectionUtils.isEmpty(keys)) {
            return new ArrayList<>();
        }
        String sql = "SELECT * FROM %s WHERE k IN (:keys)";
        sql = String.format(sql, type.getKView());
        Map<String, Object> params = new HashMap<>();
        params.put("keys", keys);
        return jdbcProxy.namedQuery(sql, params, new BeanPropertyRowMapper<>(RenaiKey.class));
    }

    public List<RenaiKey> keys(KvType type) {
        String sql = "SELECT * FROM %s";
        sql = String.format(sql, type.getKView());
        return jdbcProxy.query(sql, new BeanPropertyRowMapper<>(RenaiKey.class));
    }

    public List<RenaiKey> like(String pattern, KvType type) {
        assert StringUtils.isNotBlank(pattern);
        String sql = "SELECT * FROM %s WHERE k LIKE ?";
        sql = String.format(sql, type.getKView());
        return jdbcProxy.query(sql, new BeanPropertyRowMapper<>(RenaiKey.class), pattern);
    }

    public List<RenaiKey> regexp(String pattern, KvType type) {
        assert StringUtils.isNotBlank(pattern);
        String sql = "SELECT * FROM %s WHERE k REGEXP ?";
        sql = String.format(sql, type.getKView());
        return jdbcProxy.query(sql, new BeanPropertyRowMapper<>(RenaiKey.class), pattern);
    }

    public List<RenaiValue> values(String key, KvType type) {
        assert StringUtils.isNotBlank(key);
        String sql = "SELECT * FROM %s WHERE k = ? ORDER BY %s";
        sql = String.format(sql, type.getVView(), KvType.Map.equals(type) ? "update_time" : "score");
        return jdbcProxy.query(sql, new BeanPropertyRowMapper<>(RenaiValue.class), key);
    }

    public int total(KvType type) {
        String sql = "SELECT COUNT(id) FROM %s";
        sql = String.format(sql, type.getKView());
        return jdbcProxy.queryForObject(sql, Integer.class);
    }

    public int size(String key, KvType type) {
        assert StringUtils.isNotBlank(key);
        String sql = "SELECT COUNT(id) FROM %s WHERE k = ?";
        sql = String.format(sql, type.getVView());
        return jdbcProxy.queryForObject(sql, Integer.class, key);
    }

    public void expire(String key, int expireSeconds, KvType type) {
        assert StringUtils.isNotBlank(key) && expireSeconds > 0;
        String sql = """
                INSERT INTO %s(k, expire_time) VALUES (?, DATE_ADD(NOW(), INTERVAL ? SECOND))
                ON DUPLICATE KEY UPDATE expire_time = VALUES(expire_time), update_time = NOW()""";
        sql = String.format(sql, type.getKTable());
        jdbcProxy.update(sql, key, expireSeconds);
    }

    public boolean exists(String key, KvType type) {
        assert StringUtils.isNotBlank(key);
        String sql = "SELECT COUNT(id) FROM %s WHERE k = ?";
        sql = String.format(sql, type.getKView());
        return jdbcProxy.queryForObject(sql, Integer.class, key) > 0;
    }

    public <T> boolean contains(String key, T value, KvType type) {
        assert StringUtils.isNotBlank(key) && value != null;
        String sql = "SELECT COUNT(id) FROM %s WHERE k = ? AND v = ?";
        sql = String.format(sql, type.getVView());
        return jdbcProxy.queryForObject(sql, Integer.class, key, RenaiUtil.string(value)) > 0;
    }

    public int clear(String key, KvType type) {
        assert StringUtils.isNotBlank(key);
        String sql = "DELETE FROM %s WHERE k = ?";
        sql = String.format(sql, type.getVTable());
        return jdbcProxy.update(sql, key);
    }

    public <T> boolean remove(String key, T value, KvType type) {
        assert StringUtils.isNotBlank(key) && value != null;
        String v = RenaiUtil.string(value);
        String sql = "DELETE FROM %s WHERE k = ? AND v = ?";
        sql = String.format(sql, type.getVTable());
        return jdbcProxy.update(sql, key, v) > 0;
    }

    public <T> int removeValues(String key, Collection<T> values, KvType type) {
        assert StringUtils.isNotBlank(key);
        if (CollectionUtils.isEmpty(values)) {
            return 0;
        }
        String sql = "DELETE FROM %s WHERE k = :key AND v IN (:values)";
        sql = String.format(sql, type.getVTable());
        Map<String, Object> params = new HashMap<>();
        params.put("key", key);
        params.put("values", values.stream().map(RenaiUtil::string).toList());
        return jdbcProxy.namedUpdate(sql, params);
    }

    public boolean delete(String key, KvType type) {
        assert StringUtils.isNotBlank(key);
        return jdbcProxy.doInTransaction(() -> {
            String sql = "DELETE FROM %s WHERE k = ?";
            int count = jdbcProxy.update(String.format(sql, type.getKTable()), key);
            if (StringUtils.isNotBlank(type.getVTable())) {
                jdbcProxy.update(String.format(sql, type.getVTable()), key);
            }
            return count > 0;
        });
    }

    public int delete(Collection<String> keys, KvType type) {
        if (CollectionUtils.isEmpty(keys)) {
            return 0;
        }
        return jdbcProxy.doInTransaction(() -> {
            String sql = "DELETE FROM %s WHERE k IN (:keys)";
            Map<String, Object> params = new HashMap<>();
            params.put("keys", keys);
            int count = jdbcProxy.namedUpdate(String.format(sql, type.getKTable()), params);
            if (StringUtils.isNotBlank(type.getVTable())) {
                jdbcProxy.namedUpdate(String.format(sql, type.getVTable()), params);
            }
            return count;
        });
    }

    public void flush(KvType type) {
        jdbcProxy.doInTransaction(() -> {
            String sql = "DELETE FROM %s WHERE 1=1";
            jdbcProxy.update(String.format(sql, type.getKTable()));
            if (StringUtils.isNotBlank(type.getVTable())) {
                jdbcProxy.update(String.format(sql, type.getVTable()));
            }
            return null;
        });
    }

}
